#include "file.h"
#include "fs.h"
#include "super_block.h"
#include "inode.h"
#include "stdio-kernel.h"
#include "memory.h"
#include "debug.h"
#include "interrupt.h"
#include "string.h"
#include "thread.h"
#include "global.h"

#define DEFAULT_SECS 1

// 文件表
struct file file_table[MAX_FILE_OPEN];

// 从文件表 file_table 中获取一个空闲位，返回下标
int32_t get_free_slot_in_global(void) {
    uint32_t fd_idx = 3; // 前面 0 1 2 是标准输入输出
    while(fd_idx < MAX_FILE_OPEN) {
        if(file_table[fd_idx].fd_inode == NULL) break;
        fd_idx++;
    }
    if(fd_idx == MAX_FILE_OPEN) {
        printk("exceed max open files.\n");
        return -1;
    }
    return fd_idx;
}

// 将全局描述符下标安装到进程或线程自己的文件描述符数组 fd_table 中，返回其下标
int32_t pcb_fd_install(int32_t global_fd_idx) {
    struct task_struct* cur = running_thread();
    uint8_t local_fd_idx = 3; // 跨过标准输入输出
    while(local_fd_idx < MAX_FILES_OPEN_PER_PROC) {
        if(cur -> fd_table[local_fd_idx] == -1) { // 查找空闲位
            cur -> fd_table[local_fd_idx] = global_fd_idx;
            break;
        }
        local_fd_idx++;
    }
    if(local_fd_idx == MAX_FILES_OPEN_PER_PROC) {
        printk("exceed max open files_per_proc\n");
        return -1;
    }
    return local_fd_idx;
}

// 分配一个 inode，返回 inode 编号（位图索引）
int32_t inode_bitmap_alloc(struct partition* part) {
    int32_t bit_idx = bitmap_scan(&part -> inode_bitmap, 1);
    if(bit_idx == -1) return -1;
    bitmap_set(&part -> inode_bitmap, bit_idx, 1);
    return bit_idx;
}

// 分配一个扇区，返回其扇区地址
int32_t block_bitmap_alloc(struct partition* part) {
    int32_t bit_idx = bitmap_scan(&part -> block_bitmap, 1);
    if(bit_idx == -1) return -1;
    bitmap_set(&part -> block_bitmap, bit_idx, 1);
    return (part -> sb -> data_start_lba + bit_idx);
}

// 将内存中 bitmap 第 bit_idx 位所在的扇区同步到硬盘
void bitmap_sync(struct partition* part, uint32_t bit_idx, uint8_t btmp_type) {
    /**
     * Tips
     * 4096 = 512字节 = 1扇区
     * 但在位图(数组)中是以 0 为起始
     * 因此第 4096 位占用两个扇区
     * 0~4095 为一个扇区
    */
    uint32_t off_sec = bit_idx / 4096; // 求得 bit_idx 所在扇区
    uint32_t off_size = off_sec * BLOCK_SIZE; // 字节偏移量
    uint32_t sec_lba;     // 起始地址
    uint8_t* bitmap_off;  // 内容的起始地址

    // 需要被同步到硬盘的位图只有 inode_bitmap 和 block_bitmap
    switch(btmp_type) {
        case INODE_BITMAP:
            sec_lba = part -> sb -> inode_bitmap_lba + off_sec;
            bitmap_off = part -> inode_bitmap.bits + off_size;
            break;
        case BLOCK_BITMAP:
            sec_lba = part -> sb -> block_bitmap_lba + off_sec;
            bitmap_off = part -> block_bitmap.bits + off_size;
            break;
    }

    ide_write(part -> my_disk, sec_lba, bitmap_off, 1);
}

// 创建文件，成功则返回文件描述符
int32_t file_create(struct dir* parent_dir, char* filename, uint8_t flag) {
    void* io_buf = sys_malloc(1024);
    if(io_buf == NULL) {
        printk("in file_creat: sys_malloc for io_buf failed\n");
        return -1;
    }

    uint8_t rollback_step = 0; // 回滚点

    // 为新文件分配 inode
    int32_t inode_no = inode_bitmap_alloc(cur_part);
    if(inode_no == -1) {
        printk("in file_creat: allocate inode failed\n");
        return -1;
    }

    /**
     * 此 inode 要从堆中申请内存，不可申请局部变量，因为函数生命周期结束后会释放
     * 因为 file_table 数组中的文件描述符的 inode 指针要指向它
     */
    struct inode* new_file_inode = (struct inode*) sys_malloc(sizeof(struct inode));
    if(new_file_inode == NULL) {
        printk("file_create: sys_malloc for inode failded\n");
        rollback_step = 1;
        goto rollback;
    }
    inode_init(inode_no, new_file_inode); // 初始化 inode

    // 得到全局的文件表空闲位
    int fd_idx = get_free_slot_in_global();
    if(fd_idx == -1) {
        printk("exceed max open files\n");
        rollback_step = 2;
        goto rollback;
    }

    // 初始化文件表信息
    file_table[fd_idx].fd_inode = new_file_inode;
    file_table[fd_idx].fd_pos = 0;
    file_table[fd_idx].fd_flag = flag;
    file_table[fd_idx].fd_inode->write_deny = false;

    struct dir_entry new_dir_entry;
    memset(&new_dir_entry, 0, sizeof(struct dir_entry));

    create_dir_entry(filename, inode_no, FT_REGULAR, &new_dir_entry); // create_dir_entry只是内存操作不出意外,不会返回失败，因此不需要回滚（虽然我感觉这样不妥当？）

    // 同步目录项
    if(!sync_dir_entry(parent_dir, &new_dir_entry, io_buf)) {
        printk("sync dir_entry to disk failed\n");
        rollback_step = 3;
        goto rollback;
    }

    memset(io_buf, 0, 1024);
    inode_sync(cur_part, parent_dir -> inode, io_buf); // 同步父目录的 inode
    memset(io_buf, 0, 1024);
    inode_sync(cur_part, new_file_inode, io_buf); // 同步新文件的 inode
    bitmap_sync(cur_part, inode_no, INODE_BITMAP); // 同步 inode_bitmap 位图

    // 将新 inode 添加入已打开的 inode 链表中
    list_push(&cur_part -> open_inodes, &new_file_inode -> inode_tag);
    new_file_inode -> i_open_cnt = 1; // 打开次数设置为 1

    sys_free(io_buf);
    return pcb_fd_install(fd_idx); // 安装到当前进程的文件描述符数组中，并且返回下标

    rollback:
        switch(rollback_step) {
            case 3:
                memset(&file_table[fd_idx], 0, sizeof(struct file));
            case 2:
                sys_free(new_file_inode);
            case 1:
                bitmap_set(&cur_part -> inode_bitmap, inode_no, 0);
                break;
        }
        sys_free(io_buf);
        return -1;
}

// 打开编号为 inode_no 所对应的文件，成功返回文件描述符
int32_t file_open(uint32_t inode_no, uint8_t flag) {
    int fd_idx = get_free_slot_in_global();
    if(fd_idx == -1) {
        printk("exceed max open files.\n");
        return -1;
    }

    file_table[fd_idx].fd_inode = inode_open(cur_part, inode_no);
    file_table[fd_idx].fd_pos = 0; // 每次打开文件都要将偏移量指针重置为 0
    file_table[fd_idx].fd_flag = flag;

    // 文件是否可写，该变量指向 inode 是否可写的状态
    bool* write_deny = &file_table[fd_idx].fd_inode -> write_deny;

    if(flag & O_WRONLY || flag & O_RDWR) { // 只写 / 读写
        enum intr_status old_status = intr_disable(); // 进入临界区前先关闭中断
        if(!(*write_deny)) { // 写入
            *write_deny = true; // 设置为 true，表示不可写，避免多个进程同时对此文件进行写入
            intr_set_status(old_status);
        } else { // 写入失败
            intr_set_status(old_status);
            printk("file can`t be write now, try again later\n");
            return -1;
        }
    }
    // 若是读文件或创建文件，则不用理会 write_deny，保持默认即可

    return pcb_fd_install(fd_idx);
}

// 关闭文件
int32_t file_close(struct file* file) {
    if(file == NULL) return -1;
    file -> fd_inode -> write_deny = false;
    inode_close(file -> fd_inode);
    file -> fd_inode = NULL; // 重新使其文件结构可用
    return 0;
}

// 将 buf 中的 count 个字节写入到 file，成功则返回字节数，失败返回-1
int32_t file_write(struct file* file, const void* buf, uint32_t count) {
    if((file -> fd_inode -> i_size + count) > (BLOCK_SIZE * 140)) { // 文件目前最大只支持 512*140 字节
        printk("exceed max file_size 71680 bytes, write file failed\n");
        return -1;
    }
    uint8_t* io_buf = sys_malloc(BLOCK_SIZE);
    if(io_buf == NULL) {
        printk("file_write: sys_malloc for io_buf failed\n");
        return -1;
    }
    uint32_t* all_blocks = (uint32_t*) sys_malloc(BLOCK_SIZE + 48);
    if(all_blocks == NULL) {
        printk("file_write: sys_malloc for all_blocks failed\n");
        return -1;
    }

    const uint8_t* src = buf;	    // 用src指向buf中待写入的数据 
    uint32_t bytes_written = 0;	    // 用来记录已写入数据大小
    uint32_t size_left = count;	    // 用来记录未写入数据大小
    int32_t block_lba = -1;	        // 块地址
    uint32_t block_bitmap_idx = 0;  // 用来记录block对应于block_bitmap中的索引,做为参数传给bitmap_sync
    uint32_t sec_idx;	            // 用来索引扇区
    uint32_t sec_lba;	            // 扇区地址
    uint32_t sec_off_bytes;         // 扇区内字节偏移量
    uint32_t sec_left_bytes;        // 扇区内剩余字节量
    uint32_t chunk_size;	        // 每次写入硬盘的数据块大小
    int32_t indirect_block_table;   // 用来获取一级间接表地址
    uint32_t block_idx;		        // 块索引

    // 判断文件是否是第一次写，若是，则先分配一个块
    if(file -> fd_inode -> i_sectors[0] == 0) {
        block_lba = block_bitmap_alloc(cur_part);
        if(block_lba == -1) {
            printk("file_write: block_bitmap_alloc failed\n");
            return -1;
        }
        file -> fd_inode -> i_sectors[0] = block_lba;

        // 同步
        block_bitmap_idx = block_lba - cur_part -> sb -> data_start_lba;
        ASSERT(block_bitmap_idx != 0);
        bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
    }

    // 写入 count 个字节前，该文件已占的块数
    uint32_t file_has_used_blocks = file -> fd_inode -> i_size / BLOCK_SIZE + 1;
    // 存储 count 字节后，该文件将占用的块数
    uint32_t file_will_use_blocks = (file -> fd_inode -> i_size + count) / BLOCK_SIZE + 1;
    ASSERT(file_will_use_blocks <= 140);
    // 通过此增量判断是否需要分配新的扇区，为0则不需要分配
    uint32_t add_blocks = file_will_use_blocks - file_has_used_blocks;
    // 我得说句实话，感觉这样判断完全没必要，它们都以块为单位，得到的差不就是需要分配的新的块数吗？不需要分配的话，那不就表示两者块数相同吗？

    // -------------------------------------
    // 将所有的数据块地址收集到 all_blocks 中
    // -------------------------------------
    if(add_blocks == 0) { // 不需要分配新的块
        if(file_has_used_blocks <= 12) { // 都属于12个直接块
            block_idx = file_has_used_blocks - 1; // 指向最后一个数据扇区（即数据块）
            all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];
        } else {
            ASSERT(file -> fd_inode -> i_sectors[12] != 0);
            indirect_block_table = file -> fd_inode -> i_sectors[12]; // 得到一级间接块索引表的地址
            ide_read(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        }
    } else { // 需要分配新的块
        if(file_will_use_blocks <= 12) { // 所需要的块数直接块够用
            // 先将有剩余空间的扇区，也就是最后那个块，先写入 all_blocks
            block_idx = file_has_used_blocks - 1;
            ASSERT(file -> fd_inode -> i_sectors[block_idx] != 0);
            all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];

            // 再将未来需要用到的块写入 all_blocks
            block_idx = file_has_used_blocks;
            while(block_idx < file_will_use_blocks) {
                block_lba = block_bitmap_alloc(cur_part);
                if(block_lba == -1) {
                    printk("file_write: block_bitmap_alloc for situation 1 failed\n");
                    return -1;
                }

                // 写文件时，不应该出现块未使用，但已经分配扇区的情况，当文件删除时，就会把块地址请0
                ASSERT(file -> fd_inode -> i_sectors[block_idx] == 0); // 确保尚未分配扇区地址
                file -> fd_inode -> i_sectors[block_idx] = all_blocks[block_idx] = block_lba;

                // 同步
                block_bitmap_idx = block_lba - cur_part -> sb -> data_start_lba;
                bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

                block_idx++; // 下一个分配的新块
            }
        } else if(file_has_used_blocks <= 12 && file_will_use_blocks > 12) { // 已经使用的块数在12块之内，新增若干后，超过了12块，需要分配一级间接块
            // 道理同上，记录最后一个块，因为这个块数据未满
            block_idx = file_has_used_blocks - 1;
            all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];

            // 创建一级间接块索引表
            block_lba = block_bitmap_alloc(cur_part);
            if(block_lba == -1) {
                printk("file_write: block_bitmap_alloc for situation 2 failed\n");
                return -1;
            }

            ASSERT(file -> fd_inode -> i_sectors[12] == 0); // 确保一级间接块表未分配
            indirect_block_table = file -> fd_inode -> i_sectors[12] = block_lba;

            block_idx = file_has_used_blocks; // 第一个未使用的块，即本文件最后一个已使用的直接块的下一块
            while(block_idx < file_will_use_blocks) {
                block_lba = block_bitmap_alloc(cur_part);
                if(block_lba == -1) {
                    printk("file_write: block_bitmap_alloc for situation 2 failed\n");
                    return -1;
                }

                if(block_idx < 12) {
                    ASSERT(file -> fd_inode -> i_sectors[block_idx] == 0); // 确保尚未分配扇区地址
                    file -> fd_inode -> i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
                } else {
                    all_blocks[block_idx] = block_lba;
                }

                block_bitmap_idx = block_lba - cur_part -> sb -> data_start_lba;
                bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

                block_idx++; // 下一个新扇区
            }

            // 同步一级间接表的所有间接块
            ide_write(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        } else if(file_has_used_blocks > 12) { // 已经使用完了12个直接块，要创建一级间接块表中的间接块
            ASSERT(file -> fd_inode -> i_sectors[12] != 0); // 确保一级间接块表存在
            indirect_block_table = file -> fd_inode -> i_sectors[12]; // 获取一级间接块表

            // 读取所有间接块
            ide_read(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);

            block_idx = file_has_used_blocks; // 已使用的间接块的下一块
            while(block_idx < file_will_use_blocks) {
                block_lba = block_bitmap_alloc(cur_part);
                if(block_lba == -1) {
                    printk("file_write: block_bitmap_alloc for situation 3 failed\n");
                    return -1;
                }
                all_blocks[block_idx++] = block_lba;

                // 同步
                block_bitmap_idx = block_lba - cur_part -> sb -> data_start_lba;
                bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
            }

            // 同步所有间接块
            ide_write(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        }
    }

    bool first_write_block = true; // 含有剩余空间的扇区标识
    file -> fd_pos = file -> fd_inode -> i_size - 1;
    while(bytes_written < count) { // 直到写完所有数据
        memset(io_buf, 0, BLOCK_SIZE);
        sec_idx = file -> fd_inode -> i_size / BLOCK_SIZE;
        sec_lba = all_blocks[sec_idx];
        sec_off_bytes = file -> fd_inode -> i_size % BLOCK_SIZE;
        sec_left_bytes = BLOCK_SIZE - sec_off_bytes;

        // 判断此次写入硬盘的数据大小
        // 若剩余数据 < 扇区内剩余的内容 => size_left
        // 否则 => sec_left_bytes
        chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;

        if(first_write_block) { // 只需要执行一次，只为处理最开始记录的 all_blocks 中的那个块
            // 因为唯独这个扇区的数据可能本就不满一个扇区，也正是因为它有数据，所以要先读
            ide_read(cur_part -> my_disk, sec_lba, io_buf, 1);
            first_write_block = false; // 往后不再执行
        }

        memcpy(io_buf + sec_off_bytes, src, chunk_size);
        ide_write(cur_part -> my_disk, sec_lba, io_buf, 1);
        printk("file write at lba 0x%x\n", sec_lba);    //调试,完成后去掉

        src += chunk_size; // 对数据源偏移
        file -> fd_inode -> i_size += chunk_size; // 更新文件大小
        file -> fd_pos += chunk_size; // 文件位置指针更新
        bytes_written += chunk_size;
        size_left -= chunk_size;
    }

    inode_sync(cur_part, file -> fd_inode, io_buf);
    sys_free(all_blocks);
    sys_free(io_buf);
    return bytes_written;
}

// 从文件 file 中读取 count 个字节到 buf，返回读取的字节数，若到末尾则返回-1
int32_t file_read(struct file* file, void* buf, uint32_t count) {
    uint8_t* buf_dst = (uint8_t*) buf;
    uint32_t size = count, size_left = size;

    // 若要读取的字节数超过了文件本身的大小，则全部读取
    if((file -> fd_pos + count) > file -> fd_inode -> i_size) {
        size = file -> fd_inode -> i_size - file -> fd_pos;
        size_left = size;
        if(size == 0) return -1; // 到文件末尾了
    }

    uint8_t* io_buf = sys_malloc(BLOCK_SIZE);
    if(io_buf == NULL) {
        printk("file_read: sys_malloc for io_buf failed\n");
    }
    uint32_t* all_blocks = (uint32_t*) sys_malloc(BLOCK_SIZE + 48);
    if(all_blocks == NULL) {
        printk("file_read: sys_malloc for all_blocks failed\n");
        return -1;
    }
    uint32_t block_read_start_idx = file -> fd_pos / BLOCK_SIZE;        // 从哪个数据块开始读
    uint32_t block_read_end_idx = (file -> fd_pos + size) / BLOCK_SIZE; // 最终要读取到哪儿，落在哪个块
    uint32_t read_blocks = block_read_start_idx - block_read_end_idx; // 差为0，则表示要读取的起始和终止都在同一个块中，只读取一个扇区
    ASSERT(block_read_start_idx < 139 && block_read_end_idx < 139);

    int32_t indirect_block_table; // 一级间接块表的地址
    uint32_t block_idx;           // 待读取的块

    // 开始构建 all_blocks
    if(read_blocks == 0) { // 读取同一个扇区
        ASSERT(block_read_start_idx == block_read_end_idx);
        if(block_read_end_idx < 12) { // 直接块
            block_idx = block_read_end_idx;
            all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];
        } else { // 间接块
            indirect_block_table = file -> fd_inode -> i_sectors[block_idx];
            ide_read(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        }
    } else { // 读取多个扇区
        if(block_read_end_idx < 12) { // 起始和终止块都在12个直接块内
            block_idx = block_read_start_idx;
            while(block_idx <= block_read_end_idx) {
                all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];
                block_idx++;
            }
        } else if(block_read_start_idx < 12 && block_read_end_idx >= 12) { // 起始块在直接块中，最终要落到间接块中
            block_idx = block_read_start_idx;
            while(block_idx < 12) {
                all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];
                block_idx++;
            }
            ASSERT(file -> fd_inode -> i_sectors[12] != 0);
            indirect_block_table = file -> fd_inode -> i_sectors[12];
            ide_read(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        } else { // 起始和终止都是间接块中
            ASSERT(file -> fd_inode -> i_sectors[12] != 0);
            indirect_block_table = file -> fd_inode -> i_sectors[12];
            ide_read(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        }
    }

    // 读取数据
    uint32_t sec_idx, sec_lba, sec_off_bytes, sec_left_bytes, chunk_size;
    uint32_t bytes_read = 0;
    while(bytes_read < size) {
        sec_idx = file -> fd_pos / BLOCK_SIZE;
        sec_lba = all_blocks[sec_idx];
        sec_off_bytes = file -> fd_pos % BLOCK_SIZE;
        sec_left_bytes = BLOCK_SIZE - sec_off_bytes;
        chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;

        memset(io_buf, 0, BLOCK_SIZE);
        ide_read(cur_part -> my_disk, sec_lba, io_buf, 1);
        memcpy(buf_dst, io_buf + sec_off_bytes, chunk_size);

        buf_dst += chunk_size;
        file -> fd_pos += chunk_size;
        bytes_read += chunk_size;
        size_left -= chunk_size;
    }
    sys_free(all_blocks);
    sys_free(io_buf);
    return bytes_read;
}